// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui;

import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Arrays;
import java.util.Objects;

public abstract class PopupHandler extends MouseAdapter {
  public abstract void invokePopup(Component comp, int x, int y);

  @Override
  public void mouseClicked(MouseEvent e) {
    if (e.isPopupTrigger()) {
      invokePopup(e.getComponent(), e.getX(), e.getY());
      e.consume();
    }
  }

  @Override
  public void mousePressed(MouseEvent e) {
    if (e.isPopupTrigger()) {
      invokePopup(e.getComponent(), e.getX(), e.getY());
      e.consume();
    }
  }

  @Override
  public void mouseReleased(MouseEvent e) {
    if (e.isPopupTrigger()) {
      invokePopup(e.getComponent(), e.getX(), e.getY());
      e.consume();
    }
  }

  public static void installPopupHandler(JComponent component, @NonNls String groupId, @NonNls String place) {
    ActionManager actionManager = ActionManager.getInstance();
    ActionGroup group = (ActionGroup)actionManager.getAction(groupId);
    installPopupHandler(component, group, place, actionManager, null);
  }

  public static @NotNull MouseListener installPopupHandler(JComponent component,
                                                           @NotNull ActionGroup group,
                                                           @NonNls String place) {
    return installPopupHandler(component, group, place, null, null);
  }

  public static @NotNull MouseListener installPopupHandler(JComponent component,
                                                           @NotNull ActionGroup group,
                                                           @NonNls String place,
                                                           ActionManager actionManager) {
    return installPopupHandler(component, group, place, actionManager, null);
  }

  public static @NotNull MouseListener installPopupHandler(@NotNull JComponent component,
                                                           @NotNull ActionGroup group,
                                                           @NonNls String place,
                                                           @Nullable ActionManager actionManager,
                                                           @Nullable PopupMenuListener menuListener) {
    if (ApplicationManager.getApplication() == null) {
      return new MouseAdapter(){};
    }

    MouseListener popupHandler = new PopupHandler() {
      @Override
      public void invokePopup(Component comp, int x, int y) {
        ActionPopupMenu popupMenu = (actionManager == null ? ActionManager.getInstance() : actionManager)
          .createActionPopupMenu(place, group);
        popupMenu.setTargetComponent(component);
        JPopupMenu menu = popupMenu.getComponent();
        if (menuListener != null) {
          menu.addPopupMenuListener(menuListener);
        }
        menu.show(comp, x, y);
      }
    };
    component.addMouseListener(popupHandler);
    return popupHandler;
  }

  public static @NotNull MouseListener installFollowingSelectionTreePopup(@NotNull JTree tree,
                                                                          @NotNull ActionGroup group,
                                                                          @NonNls String place,
                                                                          @NotNull ActionManager actionManager) {
    return installConditionalPopup(tree, group, place, actionManager, (comp, x, y) -> {
      return tree.getPathForLocation(x, y) != null &&
             Arrays.binarySearch(Objects.requireNonNull(tree.getSelectionRows()), tree.getRowForLocation(x, y)) > -1;
    });
  }

  public static @NotNull MouseListener installRowSelectionTablePopup(@NotNull JTable table,
                                                                     @NotNull ActionGroup group,
                                                                     @NonNls String place,
                                                                     @NotNull ActionManager actionManager) {
    return installConditionalPopup(table, group, place, actionManager, (comp, x, y) ->
      Arrays.binarySearch(table.getSelectedRows(), table.rowAtPoint(new Point(x, y))) > -1);
  }

  public static @NotNull MouseListener installSelectionListPopup(@NotNull JList<?> list,
                                                                 @NotNull ActionGroup group,
                                                                 @NonNls String place,
                                                                 @NotNull ActionManager actionManager) {
    return installConditionalPopup(list, group, place, actionManager, (comp, x, y) -> ListUtil.isPointOnSelection(list, x, y));
  }

  private static @NotNull MouseListener installConditionalPopup(@NotNull JComponent component,
                                                                @NotNull ActionGroup group,
                                                                @NonNls String place,
                                                                @NotNull ActionManager actionManager,
                                                                @NotNull ShowPopupPredicate condition) {
    if (ApplicationManager.getApplication() == null) {
      return new MouseAdapter() {
      };
    }

    MouseListener handler = new PopupHandler() {
      @Override
      public void invokePopup(Component comp, int x, int y) {
        if (condition.shouldShowPopup(comp, x, y)) {
          ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(place, group);
          popupMenu.getComponent().show(comp, x, y);
        }
      }
    };
    component.addMouseListener(handler);
    return handler;
  }

  /**
   * @deprecated Use {@link #installUnknownPopupHandler(JComponent, ActionGroup)}
   */
  @Deprecated
  public static MouseListener installUnknownPopupHandler(JComponent component, ActionGroup group, ActionManager actionManager) {
    return installPopupHandler(component, group, ActionPlaces.UNKNOWN, actionManager, null);
  }

  public static MouseListener installUnknownPopupHandler(JComponent component, ActionGroup group) {
    return installPopupHandler(component, group, ActionPlaces.UNKNOWN, null, null);
  }

  @FunctionalInterface
  private interface ShowPopupPredicate {
    boolean shouldShowPopup(Component comp, int x, int y);
  }
}